-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Enable unused-awaitable error for mypy #14519
Conversation
It seems like usually Do we want to do the same here or relying on the git history to find this info looks good enough ? EDIT: I am planning to not squash this PR to keep the rationales around in the commit messages. |
I think a few of these are functions which are decorated by In general it seems like this has a fairly high false positive rate, which is sad to see. |
It looks like the git history just says "ignore these" -- I think squashing those all together would be fine. I suspect it would be better to include the reasoning in comments in the code or add comments to |
3f982e3
to
04c726d
Compare
Indeed not sure why I didn't noticed. So no bugs, I'll include them in the relevant commit.
I should add more details in the commit messages to explain the rationale of each type of changes.
That makes complete sense for For e39d757 it's however a bit more annoying since it is the same rationale but in quite a number of different places. |
04c726d
to
53ca28a
Compare
@MatMaul Is this worth following-up with or should we close it? |
Per #14947 (comment), my vote is for landing this. |
I've merged in develop and will fixup what I can. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks good, but I did do some of this myself---so would like another set of eyes.
AFAICS there are three categories of warnings we're suppressing here:
run_in_background
andrun_as_background_process
return deferreds so you can block until---or do something after---the background task completes.- we call a method on a Deferred which returns
self
, and mypy complains that the return value is unused - synchronous ops that return a Deferred (e.g. popping from a
Dict[str, Deferred]
)
The first class causes a lot of noise here. I wonder if we should make run_in_background
and run_as_background_process
return None
---I don't think we ever use their return values. (And if we did we could have two variants, e.g. run_in_background_and_discard
versus run_in_background_and_ which does return a deferred, say
run_in_background_returning_handler` or something.)
defer.ensureDeferred( # type: ignore[unused-awaitable] | ||
run_as_background_process("background_updates", run_background_updates) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the call to ensureDeferred
actually doing anything here? run_as_background_process
returns a Deferred and ensureDeferred
is a no-op when its argument is a Deferred.
(I also find this script really confusing---the way it starts the reactor and gets the task being run to stop the reactor!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that we can remove the ensureDeferred
call.
gc_task.start(0.1) | ||
gc_task.start(0.1) # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns a deferred which fires when the gc_task.stop()
is called---which it never is---or if the task fails. Seems safe to ignore.
@@ -635,7 +635,7 @@ async def _ctx_manager() -> AsyncIterator[None]: | |||
# writer waiting for us and it completed entirely within the | |||
# `new_defer.callback()` call above. | |||
if self.key_to_current_writer.get(key) == new_defer: | |||
self.key_to_current_writer.pop(key) | |||
self.key_to_current_writer.pop(key) # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what is going on here. I think this is safe?
Thoughts:
- I wonder if
del self.key_to_current_writer.pop[key]
would work here? - Is it possible for
self.key_to_current_writer.get(key)
to change between line 637 and 638? (I don't think so---there shouldn't be another thread accessing this?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if
del self.key_to_current_writer.pop[key]
would work here?
Looks like it should?
Is it possible for self.key_to_current_writer.get(key) to change between line 637 and 638? (I don't think so---there shouldn't be another thread accessing this?)
I don't think so either. The ReadWriteLock
is designed to be async-safe, but not thread safe.
@@ -287,7 +287,7 @@ def test_validate_config(self) -> None: | |||
"""Provider metadatas are extensively validated.""" | |||
h = self.provider | |||
|
|||
def force_load_metadata() -> Awaitable[None]: | |||
def force_load_metadata() -> "OpenIDProviderMetadata": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is mine. I think this fix is correct(?)
@@ -451,7 +451,7 @@ def test_client_does_not_retry_on_400_plus(self): | |||
self.failureResultOf(d) | |||
|
|||
def test_client_sends_body(self): | |||
defer.ensureDeferred( | |||
defer.ensureDeferred( # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have a self.get_success
around it? Or is the self.pump
below sufficient to ensure this gets called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ensureDeferred
will run the async function up until the first blocking await, ie. it should be fine.
@@ -297,7 +299,9 @@ def test_complete_appservice_txn_updates_last_txn_state( | |||
service = Mock(id=self.as_list[0]["id"]) | |||
events = [Mock(event_id="e1"), Mock(event_id="e2")] | |||
txn_id = 5 | |||
self._set_state(self.as_list[0]["id"], ApplicationServiceState.UP) | |||
self.get_success( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not sure if this needs to be here? 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look reasonable!
@@ -26,7 +26,7 @@ class DeferredCacheTestCase(TestCase): | |||
def test_empty(self) -> None: | |||
cache: DeferredCache[str, int] = DeferredCache("test") | |||
with self.assertRaises(KeyError): | |||
cache.get("foo") | |||
cache.get("foo") # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICS cache
is roughly a clever Mapping[str, Deferred]
throughout this file, and we're checking that we can get and set its contents. It's okay that we're not awaiting on them. But please double-check this.
nb: we're supposed to await |
Huh, thanks. That's news to me---the docstring at synapse/synapse/logging/context.py Lines 797 to 800 in d8cc86e
run_in_background .
|
Do you mean specifically using the Also, are the examples of using run_in_background in https://matrix-org.github.io/synapse/latest/development/synapse_architecture/cancellation.html#uncancelled-processing correct? |
the
Good question. I think those are fine because there's an implicit wait for the task to finish (or at least hit the ... actually maybe those examples aren't okay. They'll certainly not re-start any finished logging contexts, but the active logging context will get marked as finished inside the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle I like the idea of turning this check on. But there are an awful large number of false positives, which are easy to get fatigued by. I'm also fairly sure we shouldn't be discarding the result of run_in_background
.
defer.ensureDeferred( # type: ignore[unused-awaitable] | ||
run_as_background_process("background_updates", run_background_updates) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that we can remove the ensureDeferred
call.
@@ -635,7 +635,7 @@ async def _ctx_manager() -> AsyncIterator[None]: | |||
# writer waiting for us and it completed entirely within the | |||
# `new_defer.callback()` call above. | |||
if self.key_to_current_writer.get(key) == new_defer: | |||
self.key_to_current_writer.pop(key) | |||
self.key_to_current_writer.pop(key) # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if
del self.key_to_current_writer.pop[key]
would work here?
Looks like it should?
Is it possible for self.key_to_current_writer.get(key) to change between line 637 and 638? (I don't think so---there shouldn't be another thread accessing this?)
I don't think so either. The ReadWriteLock
is designed to be async-safe, but not thread safe.
@@ -451,7 +451,7 @@ def test_client_does_not_retry_on_400_plus(self): | |||
self.failureResultOf(d) | |||
|
|||
def test_client_sends_body(self): | |||
defer.ensureDeferred( | |||
defer.ensureDeferred( # type: ignore[unused-awaitable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ensureDeferred
will run the async function up until the first blocking await, ie. it should be fine.
@@ -297,7 +299,9 @@ def test_complete_appservice_txn_updates_last_txn_state( | |||
service = Mock(id=self.as_list[0]["id"]) | |||
events = [Mock(event_id="e1"), Mock(event_id="e2")] | |||
txn_id = 5 | |||
self._set_state(self.as_list[0]["id"], ApplicationServiceState.UP) | |||
self.get_success( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look reasonable!
Co-authored-by: David Robertson <davidr@element.io>
I am unsure what to do about that. |
That's probably the best way forward right now. Though I'd note that this has some implications regarding where resource usage is accounted under in metrics, and also makes it harder to determine which request kicked off which bit of processing. |
We've had this on our board for a while and I don't think we have the bandwidth to drive it forward. I'd love to see this fixed and I think we've learned useful information from this, but I think we should park this for now. If anyone gets time to look at this in the future, I'd suggest tackling one of the classes identified in #14519 (review) to hopefully make a smaller PR. |
Since I got bitten already twice by this, let's see if this check makes sense.
This is to be reviewed on a per commit basis.
Each commit is handling a different type of ignorable reported error.
There are also a couple of commits that look more like small actual bugs than false positives.
Pull Request Checklist
(run the linters)